一个诡异的拉镜像失败问题排查
Contents
问题现象
上周五有业务方反馈拉镜像失败的问题,看到的报错信息是"Bad Request”,业务方多次尝试拉取镜像均失败。
起初,笔者怀疑是Docker版本不同导致的拉镜像失败,但这个猜想瞬间被自己推翻,因为笔者在内网找到的测试机和ECS上用的Docker是相同的版本,却可以拉取成功。
最后笔者在内网机器上重新Push了镜像,暂时解决了问题,先不影响业务方的正常使用。
这就很有趣了,到底是什么原因呢?
排查过程
排查问题之前,让我们先看下镜像仓库的整体架构,如下图:
云上的镜像仓库只是作为缓存使用,真正的存储是在内网的Harbor
第一步我们尝试把问题复现,一旦稳定复现就肯定能追根溯源。
实验步骤
- 新建镜像仓库项目A
- 用Docker18.06制作镜像,并推送到A项目中
- 分别在内网机器和云上ECS请求第二步制作好的镜像
复现结果
-
在内网机器用任何Docker版本都可以拉镜像成功
-
但在ECS上使用Docker低版本1.6.2会拉镜像会失败
相关日志
第一层 在ECS Client端报错显示400错误
“Error from V2 registry: Server error: 400 trying to fetch for xxx/schedule:test201912231136”
第二层,在Registry端报错显示400错误,但是在Msg里提示Manifest不可用
“err.code”:“manifest invalid”,“err.detail”:“operation unsupported”,“err.message”:“manifest invalid”
第三层,在Registry回穿的Harbor服务上报错,显示有一层文件没找到
HEAD /v2/xxx/schedule/blobs/sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 HTTP/1.1” 404 0
分析问题原因
缩小排查范围
我们将复现出的缺失层文件:
a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
在所有的Harbor实例上去检查,发现被内网机器Docker1.6.2拉取过的Harbor实例上存在这层文件,这是一个关键点,我们去看这台Harbor实例上的日志。
filesystem.PutContent**("/docker/registry/v2/repositories/xxx/schedule/_layers/sha256/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4/link
我们知道Docker拉取一个镜像的过程,可以分为两个大步骤:
- 获取镜像的Manifest文件,包含一些元信息,以及层文件信息
- 获取Manifest中的所有层文件
在拉镜像的过程中,所有的请求都是下行请求,但是这台Harbor里的Registry出现了PutContent操作。
虽然这时还不知道根本原因,但此时我们已经把问题范围缩小到Registry。
分析源码
那下一步我们根据已有的线索去查看Registry源码,去看这台Registry为什么会执行这个操作。
正向思考
分析哪些情况下会PutContent,我们通过filesystem.PutContent
这个方法去看,可以找到很多调用的地方,通过经验适当剪枝去分析,但总归代码量有点大。
反向思考
在都是下行请求的情况下,Registry为什么会执行PutContent操作?这种不合常理的地方一般都是特例,会不会是这层文件比较特别?
我们拿着Hash值,在Registry源代码里去搜索,找到如下代码:
|
|
可以看到这里定义成了一个常量,那我们就可以顺着这个常量往上找,直到水落石出。
总结
问题特征:
ECS Client 端拉镜像失败,出现400 Bad Request。
根本原因:
云上Registry作为缓存时不能很好的支持低版本的Docker(1.10之前)
Docker Registry针对低版本Docker(1.10之前)兼容时会把Manifest格式从V2 Schema 2 转成V2 Schema 1
如果镜像历史里有Empty Layer(大小为0)就会分为两种情况:
-
内网Harbor里的Registry会执行PutContent操作,进行自动填补(所以内网任何Docker版本都拉镜像成功) 相关源码片段 docker/distribution/manifest/schema1/config_builder.go +124
-
云上Registry因为作为缓存模式来使用,针对低版本的Docker转换格式之后会直接穿透回内网,而此时内网没有这层文件,最终导致了拉镜像失败。
问题到这里就水落石出了,把问题搞个一清二楚,舒坦~
话说现在还有人在用1.6.2版本的Docker么,赶紧推进升级吧
Author David
LastMod 2020-01-03